<?php
/**
 * 常用的工具类
 * @Author: zhanghongchen
 */
namespace library;
use Carbon\Carbon;
use SebastianBergmann\GlobalState\Exception;
use think\Log;

class Helpers 
{
    
    /**
     * 修正累计误差
     * @param array $items
     * @param string $total
     * @param int $scale
     * @return array
     */
    public static function ladder(array $items, string $total, int $scale)
    {
        $ladder_sum = 0;
        $last_index = count($items) - 1;

        for ($i = 0; $i < $last_index; $i++) {
            $items[$i] = bcadd(0, $items[$i], $scale);
            $ladder_sum = bcadd($ladder_sum, $items[$i], $scale);
        }

        $items[$last_index] = bcsub($total, $ladder_sum, $scale);
        return $items;
    }
    
    /**
    * 奇妙魔方-生成和等于指定值的数组
    * @param $totalSum
    * @param $len
    * @param int $range
    * @return array
    */
    public static function magicSquare($totalSum, $len, $range = 5) {
        $center = floor($totalSum/$len);

        if ($range > $center) {
            $range = (int)ceil($center*0.5);
        }

        if ($totalSum <= $len * 2) {
            $range = 0;
        }

        $newArr = [];
        $newArr = array_pad($newArr, $len, $center);
        $diffNum = $totalSum - array_sum($newArr);

        $newArr[0] += $diffNum;
        if ($range == 0) {
            $randArr = [0];
        } else {
            $randArr = array_flip(range(1, $range));
        }

        $lastValue = null;

        foreach($newArr as &$value) {
            if (!$lastValue) {
                $lastValue = array_rand($randArr, 1);
                $value+=$lastValue;
            } else {
                $value-=$lastValue;
                $lastValue=null;
            }
        }

        if ($lastValue) {
            $lastIndex = count($newArr)-1;
            $newArr[$lastIndex] -= $lastValue;
        }

        shuffle($newArr);
        return $newArr;
    }
    
    /**
     * 格式化资金相关的数字
     *   以千位分隔符, 取两位小数
     * @param float $number
     * @return string
     */
    public static function formatNumber($number) {
        return number_format($number, 2, '.', ',');
    }
    
    /**
    * 字符串替换*号
    * @param string $str 替换开头结尾以外的字符为*号
    * @param number $start 开头保留位数
    * @param number $end 结尾保留位数
    * @return string
    */
    public static function filterString($str, $start = 0, $end = 0){
        $starStr = $endStr = '';

        if ($start > 0)  $starStr = mb_substr($str, 0, $start);

        if ($end > 0) $endStr = mb_substr($str, -$end, $end);

        if (($start == 0) && ($end == 0)) {
                $hideCount = 0;
        } else {
                $hideCount = strlen($str) - strlen($starStr) - strlen($endStr)>6?6:strlen($str) - strlen($starStr) - strlen($endStr);
        }
        
        return $hideCount <= 0? $str : ($starStr . str_repeat('*', $hideCount) . $endStr);
    }

    /**
     * 装饰姓名
     * @param        $name
     * @param string $replaceStr
     * @return string
     */
    public static function filterName($name,$replaceStr = '**'){
		if (!$name || $name == null || $name == ''){
			return '';
		}
		$strLength = mb_strlen($name, 'utf-8');
		if ($strLength < 1){
			return '';
		}
		$firstStr = mb_substr($name, 0, 1, 'utf-8');
		$lastStr = mb_substr($name, -1, 1, 'utf-8');
		return $strLength == 2
			? $firstStr . str_repeat('*', mb_strlen($name, 'utf-8') - 1)
			: $firstStr . str_repeat($replaceStr,$strLength - 2);
    }
    

    /**
     * 随机字串生成器
     *
     * @param int $length
     * @return string
     */
    public static function randomString($length, $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') {
        $charactersLength = strlen($characters);
        $rs = '';
        for ($i = 0; $i < $length; $i++) {
            $rs .= $characters[rand(0, $charactersLength - 1)];
        }
        return $rs;
    }

    /**
     * 主键序号生成器
     *
     * @param string $type
     * @return mixed 20位数字
     */
    public static function serialGen($type = '') {
        if(!function_exists('dk_get_next_id')) {
            die('please load donkeyid');
        }

        $redis = Redis::getInstance();
        $primaryKey = $redis->rPop(RedisKey::HLC_CLI_REQUEST_PRIMARY_KEY);
        if(!$primaryKey){
            self::genPrimaryKeyForCLI(50000);
            $rawArray = $redis->getRedis()->brPop([RedisKey::HLC_CLI_REQUEST_PRIMARY_KEY],10);
            if ($rawArray['1']){
                return $rawArray['1'];
            }else{
                throw new \think\Exception('生成全局唯一id失败！');
            }
        }
        return $primaryKey;
    }

    /**
     * 生成cli主键
     * $max 生成数量
     */
    public static function genPrimaryKeyForCLI($max)
    {
        $redis =Redis::getInstance();
        #锁
        if(!$redis->getRedis()->setnx(RedisKey::HLC_CLI_REQUEST_PRIMARY_KEY_LOCK,1)){#不可以执行 没有获取到锁
            return;
        }else{#获取到锁 设置有效时间 防止下面执行报错
            $redis->expire(RedisKey::HLC_CLI_REQUEST_PRIMARY_KEY_LOCK,60);
        }

        $count = $redis->lLen(RedisKey::HLC_CLI_REQUEST_PRIMARY_KEY);

        if( $count < $max){
            $genNum = ($max - $count);
            for ($i = 0; $i < $genNum; $i++) {
                $dataRaw[] = dk_get_next_id();
            }
            $redis->getRedis()->lpush(RedisKey::HLC_CLI_REQUEST_PRIMARY_KEY, ...$dataRaw);
        }

        $redis->getRedis()->del(RedisKey::HLC_CLI_REQUEST_PRIMARY_KEY_LOCK);
    }

    /**
     * 生成跟时间相关唯一23位流水号 类似于： 17042115560312345678900 不带业务前缀
     *
     * @return string
     */
    public static function buildSerialNumber($len = 23) {
        list($usec, $sec) = explode(' ', microtime());
        $msec = round($usec * 1000);
        $sn = round(11111111,99999999);
        $sn = substr($sn, 0, $len); // $sn = date('ymdHis').str_pad($msec, 3, '0', STR_PAD_LEFT).str_pad(mt_rand(1, 99999999), 8, '0', STR_PAD_LEFT);
        return $sn;
    }

    /**
     * 订单生成助手函数
     *
     * @param string $type 订单前缀,具体参考配置 extra\ultron.php order_type 配置
     * @return string|null 满足前缀字典则返回25位字符串，否则返回null
     */
    public static function orderNo($type = 'WZ') {
        $types = config('ultron.order_type');
        if (in_array($type, $types)) {
            $sn = $type.self::buildSerialNumber();
            return $sn;
        } else {
            throw new \Exception($type.'未在config定义！');
        }
    }

    /**
     * 日期时间生成器
     *
     * @return string
     */
    public static function dateTime() {
        return date('Y-m-d H:i:s');
    }

    /**
	 * 获取毫秒级时间戳
	 * @return float
	 */
	public static function getMillisecond() {
		list($s1, $s2) = explode(' ', microtime());
		return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000);
	}

    /**
     * 统一获取订单号(银行)
    */
    public  static function getUniformOrderId($business, $orderId = '', $clearPrefix = 0){
        if( !$business) {
            return false;
        }
        
        switch($business) {
            case 'freeze':
                $prefix = 'FR'.date('His');
                break;
            case 'invest':
                $prefix = 'IT'.date('His');
                break;
            case 'transfer':
                $prefix = 'TR'.date('His');
                break;
            case 'loan':
                $prefix = 'LN'.date('His');
                break;
            case 'repay'://借款人还款
                $prefix = 'RT'.date('His');
                break;
            case 'guaranteeRepay':  //担保户还款
                $prefix = 'GR'.date('His');
                break;
            case 'interest':  //基本派息
                $prefix = 'PX'.date('His');
                break;
            case 'extraInterest':  //额外派息
                $prefix = 'EPX'.date('His');
                break;
            default:
                $prefix = 'DT'.date('His'); // default
                break;
        }

        // 获取去掉前缀后的订单号
        if($clearPrefix == 1) {
            return substr($orderId, strlen($prefix));
        }

        if( !$orderId) {
            return $prefix . '_' . strtoupper(substr(md5(5566 .time(). uniqid()), 0, 15));
        }
        
        return $prefix . $orderId;
    }
    
    /**
     * 邀请码 -> uid
     *
     * @return int
     */
    public static function recommendId($platform, $code) {
        $code = trim($code);
        if(!$code) return 0;

        if($platform == 1) {
            if( is_numeric($code) ) {
                if(strlen($code) == 11 && preg_match('/^1[3456789][\d]{9}$/', $code) ) { //手机号
                    $uid = DB::table('user_info')->where(['ui_phone' => $code])->first();
                    if(!$uid) { // 邀请码为手机号但没找到邀请人
                        // TODO: log
                        return 0;
                    }
                    return $uid;
                }elseif(preg_match('/^800[\d]+$/', $code)){
                    return substr($code, 3);
                }
            }else{ //多邀多得
                $code_map = [ 'N' => '0', 'H' => '1', 'k' => '3', 'Z' => '4', 'i' => '5', 'X' => '6', 'c' => '7', 'L' => '8', 'm' => '9', ];
                $uid = implode('', array_map(function($a) use($code_map) {
                    return $code_map[$a] ?? $a;
                }, str_split($code, 1)));
                return $uid;
            }
        }

        return $code;
    }

    /**
     * 永利宝 与 火理财 平台 密码加密器
     * 
     * @param  string $password 显式密码
     * @return string 加密之后的密码
     */
    public static function encryptPassword($password) {
        return md5(substr(md5($password), 7, -7));
    }

    /**
     * 获取ip
     *
     * @return string
     */
    public static function getUserIp() {
        if (!empty($_SERVER["HTTP_X_REAL_IP"])) {
            return $_SERVER["HTTP_X_REAL_IP"];
        }
        if (!empty($_SERVER["HTTP_X_FORWARDED_FOR"])) {
            return $_SERVER["HTTP_X_FORWARDED_FOR"];
        }
        if (!empty($_SERVER["HTTP_CLIENT_IP"])) {
            return $_SERVER["HTTP_CLIENT_IP"];
        }
        if (!empty($_SERVER["REMOTE_ADDR"])) {
            return $_SERVER["REMOTE_ADDR"];
        } else {
            return "-";
        }
    }

    /**
     * @param string $path  绝对路径
     * @param array  $params URL参数
     */
    public static function urlPath($path, $params = []){
        $url = env('APP_URL') ?? \think\Request::instance()->domain();
        if(env('APP_PLATFORM_NAME')) {
            $platform = '';
        }else{
            preg_match('/(inside|ylb|hlc)/', debug_backtrace()[1]['class'], $matched);
            if(!isset($matched[1]))
                throw new LogicException('urlPath 不能在公共逻辑调用！', -99);

            $platform = $matched[1];
            $url = $url.'/http/'.$platform;
        }
        if(!preg_match('/^\//', $path)) $path = '/' . $path;

        return $url.$path.'?'.http_build_query($params);
    }

    /**
     * @desc    : 通过卡号得到银行编码
     * @date    : 2017-09-05 15:30:45
     * @author  : lawson.zhang<zhangzhihong@ylb.net>
     * @version : 1.0.0.0
     * @param   : string $bankCard
    */
    public static function getBankCodeByCard($bankCard)
    {
        $bankInfo = json_decode(file_get_contents('https://ccdcapi.alipay.com/validateAndCacheCardInfo.json?_input_charset=utf-8&cardNo=' . $bankCard . '&cardBinCheck=true'), true);
        return isset($bankInfo['bank']) ? $bankInfo['bank'] : '';
    }

    //保护姓名
    public static function safeName($name)
    {
        return mb_substr($name,0,1).'**';
    }

    //保护手机号隐私,保留头尾2个
    public static function safePhone($phone,$replace = '*******')
    {
        return substr_replace($phone,$replace,2,7);
    }

	//保护手机号隐私,保留头尾4个
	public static function safeForPhone($phone,$replace = '****')
	{
		return substr_replace($phone,$replace,3,4);
	}

    //保护身份证隐私
    public static function safeIDCard($id_card,$replace = '*******')
    {
        return substr_replace($id_card,$replace,3,11);
    }

    /**
     * 得到还款方式的描述
     */
    public static function paybackMethodDes($paybackMethod)
    {
        $methods = str_split($paybackMethod, 1);
        $methodDesc = [
            1 => '到期还本付息',
            2 => '先息后本',
            3 => '等本等息',
            4 => '等额本息',
            5 => '等额本金',
            6 => '月息季本',
        ];

        $result = '';
        foreach ($methods as $key => $_method) {
            $result .= (isset($methodDesc[$_method]) ? $methodDesc[$_method] : '') . ',';
        }

        return rtrim($result, ',');
    }

    /**
     * 获取永利宝VIP计算比
     */
    public static function getVipLevel($level)
    {
        switch ($level) {
            case 'VIP1':
                return 1.2;
                break;

            case 'VIP2':
                return 1.2;
                break;

            case 'VIP3':
                return 1.2;
                break;

            case 'SVIP1':
                return 1.4;
                break;

            case 'SVIP2':
                return 1.4;
                break;

            case 'SVIP3':
                return 1.4;
                break;

            default:
                break;
        }
        return 1;
    }

    /**
     * 去掉数字多余的'0'
     * @param float $number
     * @return mixed|string
     */
    public static function del0($number)
    {
        $s = trim(strval($number));
        if (preg_match('#^-?\d+?\.0+$#', $s)) {
            return preg_replace('#^(-?\d+?)\.0+$#','$1',$s);
        }
        if (preg_match('#^-?\d+?\.[0-9]+?0+$#', $s)) {
            return preg_replace('#^(-?\d+\.[0-9]+?)0+$#','$1',$s);
        }
        return $s;
    }


    /**
     * 生成时间序点
     * @param Carbon $dateBegin
     * @param Carbon $dateEnd
     * @param int $monthlyDay
     * @return array
     */
    public static function calcRepayDates(Carbon $dateBegin, Carbon $dateEnd, $monthlyDay = 30)
    {
        if (!$monthlyDay) {
            $monthlyDay = $dateBegin->day;
        }

        $dates = [$dateBegin->copy()];

        if ($dateBegin->day < $monthlyDay) {
            $monthBegin = self::setDay($dateBegin->copy(), $monthlyDay);
        } else {
            $monthBegin = self::setDay($dateBegin->addMonthNoOverflow(), $monthlyDay);
        }

        while ($monthBegin->lt($dateEnd)) {
            $dates[] = self::setDay($monthBegin->copy(), $monthlyDay);
            $monthBegin = $monthBegin->addMonthNoOverflow();
        }

        if (!$dateEnd->eq(end($dates))) {
            $dates[] = $dateEnd->copy();
        }

        return $dates;
    }

    public static function setDay(Carbon $date, int $day)
    {
        $last_day = $date->lastOfMonth()->day;
        if ($day > $last_day) {
            $day = $last_day;
        }
        return $date->day($day);
    }



    /**
     * 身份证号生成器
     * @return string
     */
    public static function idFaker()
    {
        $Year_start = mktime(0,0,0,1,1,1950);
        $Year_end = mktime(0,0,0,12,31,2003);
        $Region= '110101';

        $Birth = mt_rand($Year_start,$Year_end);
        $Birth_format = date('Ymd',$Birth);
        $suffix_a = mt_rand(0,9);
        $suffix_b = mt_rand(0,9);
        $suffix_c = mt_rand(0,9);//male or female
        $base = $Region.$Birth_format.$suffix_a.$suffix_b.$suffix_c;

        if (strlen($base) <> 17){
            die('Invalid Length');
        }
        $factor = array(7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2);
        $sums = 0;
        for ($i=0;$i< 17;$i++){

            $sums += substr($base,$i,1) * $factor[$i];
        }

        $mods = $sums % 11;//10X98765432

        switch ($mods){
            case 0: $base.='1';break;
            case 1: $base.='0';break;
            case 2: $base.='x';break;
            case 3: $base.='9';break;
            case 4: $base.='8';break;
            case 5: $base.='7';break;
            case 6: $base.='6';break;
            case 7: $base.='5';break;
            case 8: $base.='4';break;
            case 9: $base.='3';break;
            case 10: $base.='2';break;
        }

        return $base;
    }

    /**
     * 根据 身份证号判断用户性别
     */
    public static function getGenderFromId($id)
    {
        if(strlen($id)==18){
            $flag = substr(trim($id),-2,1);

        }elseif(strlen($id)==15){
            $flag = substr(trim($id),-1,1);
        }else{
            return 0;
        }

        if($flag%2 ==0){
            $gender = 2 ;//女
        }else{
            $gender = 1 ; //男
        }
        return $gender;
    }

    /**
     * 合并两数组，已第一个数组为key，第二数组为值
     * @param array $keyArr 键值数组
     * @param array $valArr 值数组
     * @return array 合并后数组
     */
    public static function arrayCombine($keyArr, $valArr){
        $retArr = [];

        if (count($keyArr) == count($valArr)) {
            $retArr = array_combine($keyArr, $valArr);
        }
        
        return $retArr;
    }

	/**
	 * 计算两个时间戳 1 <1小时，显示为**分**秒 2 1小时<募满用时<24小时，显示为：**小时**分，不够一分的算一分钟； 3 *天**小时，不够一小时的算一小时；
	 * @param time $beginTime 开始时间戳
	 * @param array $endTime 结束时间戳
	 * @return array 结果集
	 */
	public static function timediff($begin_time,$end_time)
	{
		if($begin_time < $end_time){
			$starttime = $begin_time;
			$endtime = $end_time;
		}else{
			$starttime = $end_time;
			$endtime = $begin_time;
		}

        //计算天数
		$timediff = $endtime-$starttime;
		$days = intval($timediff/86400);
        //计算小时数
		$remain = $timediff%86400;
		$hours = intval($remain/3600);
        //计算分钟数
		$remain = $remain%3600;
		$mins = intval($remain/60);
       //计算秒数
		$secs = $remain%60;
		$str='';
        if($days>0){
			$str = $days.'天'.($mins > 0 ?($hours+1).'小时':$hours.'小时');
		}
		if($days <= 0 && $hours >= 0){
			$str = $hours.'小时'.($secs > 0 ?($mins+1).'分':$mins.'分');
		}
		if($days <= 0 && $hours <= 0){
			$str = $mins.'分'.$secs.'秒';
		}

		$res = array("day" => $days,"hour" => $hours,"min" => $mins,"sec" => $secs);
		return $str;
	}

    /*
     * 抽奖
     */
    public static function lottery($config) {
        $result = 0;
        $sum = array_sum($config);
        foreach ($config as $key => $proCur) {
            $randNum = mt_rand(1, $sum);
            if ($randNum <= $proCur) {
                $result = $key;
                break;
            } else {
                $sum -= $proCur;
            }
        }
        return $result;
    }

	/**
	 *数字金额转换成中文大写金额的函数
	 *String Int $num 要转换的小写数字或小写字符串
	 *return 大写字母
	 *小数位为两位
	 **/
	public static function numToRmb($num){
		$c1 = "零壹贰叁肆伍陆柒捌玖";
		$c2 = "分角元拾佰仟万拾佰仟亿";
		//精确到分后面就不要了，所以只留两个小数位
		$num = round($num, 2);
		//将数字转化为整数
		$num = $num * 100;
		if (strlen($num) > 10) {
			return "金额太大，请检查";
		}
		$i = 0;
		$c = "";
		while (1) {
			if ($i == 0) {
				//获取最后一位数字
				$n = substr($num, strlen($num) - 1, 1);
			} else {
				$n = $num % 10;
			}
			//每次将最后一位数字转化为中文
			$p1 = substr($c1, 3 * $n, 3);
			$p2 = substr($c2, 3 * $i, 3);
			if ($n != '0' || ($n == '0' && ($p2 == '亿' || $p2 == '万' || $p2 == '元'))) {
				$c = $p1 . $p2 . $c;
			} else {
				$c = $p1 . $c;
			}
			$i = $i + 1;
			//去掉数字最后一位了
			$num = $num / 10;
			$num = (int)$num;
			//结束循环
			if ($num == 0) {
				break;
			}
		}
		$j = 0;
		$slen = strlen($c);
		while ($j < $slen) {
			//utf8一个汉字相当3个字符
			$m = substr($c, $j, 6);
			//处理数字中很多0的情况,每次循环去掉一个汉字“零”
			if ($m == '零元' || $m == '零万' || $m == '零亿' || $m == '零零') {
				$left = substr($c, 0, $j);
				$right = substr($c, $j + 3);
				$c = $left . $right;
				$j = $j - 3;
				$slen = $slen - 3;
			}
			$j = $j + 3;
		}
		//这个是为了去掉类似23.0中最后一个“零”字
		if (substr($c, strlen($c) - 3, 3) == '零') {
			$c = substr($c, 0, strlen($c) - 3);
		}
		//将处理的汉字加上“整”
		if (empty($c)) {
			return "零元整";
		} else {
			return $c . "整";
		}
	}

	/**
	 * 二维数组根据字段进行排序
	 * @params array $array 需要排序的数组
	 * @params string $field 排序的字段
	 * @params string $sort 排序顺序标志 SORT_DESC 降序；SORT_ASC 升序
	 */
	public static function arraySequence($array, $field, $sort = 'SORT_DESC')
	{
		$arrSort = array();
		foreach ($array as $uniqid => $row) {
			foreach ($row as $key => $value) {
				$arrSort[ $key ][ $uniqid ] = $value;
			}
		}
		array_multisort($arrSort[ $field ], constant($sort), $array);
		return $array;
	}
        
    /**
     * 日期天数差
     * @param Carbon $beganDay
     * @param Carbon $endDay
     * @return numeric
     */
	public static function calculationDay($beganDay, $endDay)
	{

            $beganDay = new Carbon(date('Y-m-d', strtotime($beganDay)));
            $endDay = new Carbon(date('Y-m-d', strtotime($endDay)));
            $days = $endDay->diffInDays($beganDay);
            return $days;
	}
        
    /**
     * 生成运签合同编号
     * @param $key  健值
     * @param $date 时间
     * @return string
     */
    public static function createContractNumber($key, $date)
    {
        $redis = Redis::getInstance();
        $dateLine = date('Ymd', strtotime($date));
        $data = $dateLine.'0000000';
        $rs = $redis->hget($key,$dateLine);
        
        if(!$rs) {
            $data += 1;
        }else{
            $data = $rs+1;
        }

        $redis->hset($key, $dateLine, $data);
        $redis->expire($key, 3600*24*365);
        return $key.$data;
    }

	/**
	 * 组装新的数组
	 * @param $arr   数组只能保持一个只有一个值
	 * @param $date 需要添加的字符串
	 * @return string
	 */
	public static function addVauleStr($arr, $str)
	{
		  if(!$arr)   return $arr;
		  foreach ($arr as $k =>$v){
			  $arr[$k]=$str.$v;
		  }
		  return $arr;
	}
	
	/**
	 * 自定义排序函数
	 * @author zhouweiphp
	 * @param \Closure $fn 判断函数,返回bool,true:表示替换
	 * @param array $arr 数组
	 * @throws \Exception
	 * @return array
	 * 2018年5月19日 上午11:23:37
	 */
	static function arraySort($fn, $arr) {
	    $len = count($arr);
	    $flag = true;
	    for($i=0; $i<$len && $flag; $i++) {
	        $flag = false;
	        for($j=$len-1; $j>$i; $j--) {
	            $compare = $fn($arr[$j-1], $arr[$j]);
	            if (!is_bool($compare)) {
	                throw new \Exception('比较器返回值不为bool');
	            }
	            if ($compare) {
	                $flag = true;
	                $tmp = $arr[$j];
	                $arr[$j] = $arr[$j-1];
	                $arr[$j-1] = $tmp;
	            }
	        }
	    }
	    return $arr;
	}
}

